#include "BigUnsignedInABase.hh"

BigUnsignedInABase::BigUnsignedInABase(const Digit *d, Index l, Base base)
        : NumberlikeArray<Digit>(d, l), base(base) {
    // Check the base
    if (base < 2)
        throw "BigUnsignedInABase::BigUnsignedInABase(const Digit *, Index, Base): The base must be at least 2";

    // Validate the digits.
    for (Index i = 0; i < l; i++)
        if (blk[i] >= base)
            throw "BigUnsignedInABase::BigUnsignedInABase(const Digit *, Index, Base): A digit is too large for the specified base";

    // Eliminate any leading zeros we may have been passed.
    zapLeadingZeros();
}

namespace {
    unsigned int bitLen(unsigned int x) {
        unsigned int len = 0;
        while (x > 0) {
            x >>= 1;
            len++;
        }
        return len;
    }

    unsigned int ceilingDiv(unsigned int a, unsigned int b) {
        return (a + b - 1) / b;
    }
}

BigUnsignedInABase::BigUnsignedInABase(const BigUnsigned &x, Base base) {
    // Check the base
    if (base < 2)
        throw "BigUnsignedInABase(BigUnsigned, Base): The base must be at least 2";
    this->base = base;

    // Get an upper bound on how much space we need
    int maxBitLenOfX = x.getLength() * BigUnsigned::N;
    int minBitsPerDigit = bitLen(base) - 1;
    int maxDigitLenOfX = ceilingDiv(maxBitLenOfX, minBitsPerDigit);
    len = maxDigitLenOfX; // Another change to comply with `staying in bounds'.
    allocate(len); // Get the space

    BigUnsigned x2(x), buBase(base);
    Index digitNum = 0;

    while (!x2.isZero()) {
        // Get last digit.  This is like `lastDigit = x2 % buBase, x2 /= buBase'.
        BigUnsigned lastDigit(x2);
        lastDigit.divideWithRemainder(buBase, x2);
        // Save the digit.
        blk[digitNum] = lastDigit.toUnsignedShort();
        // Move on.  We can't run out of room: we figured it out above.
        digitNum++;
    }

    // Save the actual length.
    len = digitNum;
}

BigUnsignedInABase::operator BigUnsigned() const {
    BigUnsigned ans(0), buBase(base), temp;
    Index digitNum = len;
    while (digitNum > 0) {
        digitNum--;
        temp.multiply(ans, buBase);
        ans.add(temp, BigUnsigned(blk[digitNum]));
    }
    return ans;
}

BigUnsignedInABase::BigUnsignedInABase(const std::string &s, Base base) {
    // Check the base.
    if (base > 36)
        throw "BigUnsignedInABase(std::string, Base): The default string conversion routines use the symbol set 0-9, A-Z and therefore support only up to base 36.  You tried a conversion with a base over 36; write your own string conversion routine.";
    // Save the base.
    // This pattern is seldom seen in C++, but the analogous ``this.'' is common in Java.
    this->base = base;

    // `s.length()' is a `size_t', while `len' is a `NumberlikeArray::Index',
    // also known as an `unsigned int'.  Some compilers warn without this cast.
    len = Index(s.length());
    allocate(len);

    Index digitNum, symbolNumInString;
    for (digitNum = 0; digitNum < len; digitNum++) {
        symbolNumInString = len - 1 - digitNum;
        char theSymbol = s[symbolNumInString];
        if (theSymbol >= '0' && theSymbol <= '9')
            blk[digitNum] = theSymbol - '0';
        else if (theSymbol >= 'A' && theSymbol <= 'Z')
            blk[digitNum] = theSymbol - 'A' + 10;
        else if (theSymbol >= 'a' && theSymbol <= 'z')
            blk[digitNum] = theSymbol - 'a' + 10;
        else
            throw "BigUnsignedInABase(std::string, Base): Bad symbol in input.  Only 0-9, A-Z, a-z are accepted.";

        if (blk[digitNum] >= base)
            throw "BigUnsignedInABase::BigUnsignedInABase(const Digit *, Index, Base): A digit is too large for the specified base";
    }
    zapLeadingZeros();
}

BigUnsignedInABase::operator std::string() const {
    if (base > 36)
        throw "BigUnsignedInABase ==> std::string: The default string conversion routines use the symbol set 0-9, A-Z and therefore support only up to base 36.  You tried a conversion with a base over 36; write your own string conversion routine.";
    if (len == 0)
        return std::string("0");
    // Some compilers don't have push_back, so use a char * buffer instead.
    char *s = new char[len + 1];
    s[len] = '\0';
    Index digitNum, symbolNumInString;
    for (symbolNumInString = 0; symbolNumInString < len; symbolNumInString++) {
        digitNum = len - 1 - symbolNumInString;
        Digit theDigit = blk[digitNum];
        if (theDigit < 10)
            s[symbolNumInString] = char('0' + theDigit);
        else
            s[symbolNumInString] = char('A' + theDigit - 10);
    }
    std::string s2(s);
    delete[] s;
    return s2;
}
